/* 
   Copyright 2012 Ryne Okimoto

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

package recipe;

/**
 * Class - Ingredient
 * Description - Class to represent an ingredient
 * @author Ryne Okimoto
 */

public class Ingredient {
	/*
	 * 1 cup = 8 fl oz
	 * 1 pint = 2 cups
	 * 1 quart = 2 pints
	 * 4 cups = 1 quart
	 * 1 gallon = 4 quarts
	 * 
	 * 16 oz = 1 lb
	 * ***********************
	 * 
	 * standard = metric
	 * 
	 * fluid oz = mL, multiply oz by 30 to get mL
	 * gal = L, multiply gal by 3.8 to get L
	 * qt = L, multiply qt by 0.9 to get L
	 * 
	 * oz = g, multiply oz by 28 to get g
	 * lb = kg, multiply by 0.45 to get kg
	 * 
	 */
	
	public static final String UNIT_QUANTITY = "";
	
	public static final String UNIT_METRIC_ML = "mL";
	public static final String UNIT_METRIC_L = "L";
	public static final String UNIT_METRIC_GRAM = "g";
	public static final String UNIT_METRIC_KILOGRAM = "kg";
	
	public static final String UNIT_STANDARD_FL_OZ = "fl oz";
	public static final String UNIT_STANDARD_CUP = "c";
	public static final String UNIT_STANDARD_PINT = "pt";
	public static final String UNIT_STANDARD_QUART = "qt";
	public static final String UNIT_STANDARD_GALLON = "gal";
	public static final String UNIT_STANDARD_TEASPOON = "tsp";
	public static final String UNIT_STANDARD_TABLESPOON = "Tbsp";
	public static final String UNIT_STANDARD_OZ = "oz";
	public static final String UNIT_STANDARD_POUND = "lb";
	
	// Ingredient attributes
	private String ingredientName;
	private String ingredientAmount;
	private String ingredientUnit;
	private boolean isMetric;
	// TODO implement to help with scaling
	// private boolean amountIsFraction;
	// private boolean amountIsDecimal;
	// private boolean amountIsInteger;
	
	/**
	 * Default constructor for Ingredient class
	 */
	public Ingredient(){
		ingredientName = null;
		ingredientAmount = null;
		ingredientUnit = null;
		isMetric = false;
	} // constructor
	
	/**
	 * Overloaded constructor for Ingredient class
	 * 
	 * Description: Initializes data fields for Ingredient
	 *     Assumes all fields are correct
	 * @param name
	 * @param amount
	 * @param unit
	 */
	public Ingredient(String name, String amount, String unit){
		ingredientName = name;
		ingredientAmount = amount;
		ingredientUnit = unit;
		isMetric = isUnitMetric(unit);
	} // constructor
	
	/**
	 * Method - toString
	 * Description - Generates a string representation of the ingredient
	 * @override
	 */
	public String toString(){
		return 
			ingredientAmount + " " + 
			ingredientUnit + " " +
			ingredientName;
	}
	
	/**
	 * Method - validateUnit
	 * Description - Checks if a unit is valid
	 * @param unit
	 * @return True - Returns if the unit is valid
	 * @return False - Returns if the unit is not valid
	 */
	public static boolean validateUnit(String unit){
		return 
			unit.equals(UNIT_METRIC_ML) ||
			unit.equals(UNIT_METRIC_L) ||
			unit.equals(UNIT_METRIC_GRAM) ||
			unit.equals(UNIT_METRIC_KILOGRAM) ||
			unit.equals(UNIT_STANDARD_CUP) ||
			unit.equals(UNIT_STANDARD_FL_OZ) ||
			unit.equals(UNIT_STANDARD_GALLON) ||
			unit.equals(UNIT_STANDARD_OZ) ||
			unit.equals(UNIT_STANDARD_PINT) ||
			unit.equals(UNIT_STANDARD_POUND) ||
			unit.equals(UNIT_STANDARD_QUART) ||
			unit.equals(UNIT_STANDARD_TABLESPOON) ||
			unit.equals(UNIT_STANDARD_TEASPOON) ||
			unit.equals(UNIT_QUANTITY);
	}
	
	/**
	 * Method - parseAmount
	 * Description - Parses a fraction or decimal string into a double
	 * @param amount
	 * @return 0 - if the amount is not a valid fraction or decimal
	 * @return double - the amount as a decimal if the string is a valid fraction or decimal
	 */
	public static double parseAmount(String amount){
		double num = 0, den = 0;
		String[] fraction = null;
		double value = 0;
		boolean isMatch = false;
		boolean isFraction = false;
		boolean isDecimal = false;
		boolean isInteger = false;
		
		// Integer - one or more digits
		isInteger = amount.matches("\\d++");
		// Decimal number - one or more digits 
		// followed by a period followed by 
		// one or more digits
		isDecimal = amount.matches("\\d++\\.\\d++");
		// Fraction - one or more digits 
		// followed by a '/' followed by 
		// one or more digits
		isFraction = amount.matches("\\d++/\\d++");
		
		isMatch = isInteger || isDecimal || isFraction;	
		
		if(isMatch){
			if(isInteger){
				value = Integer.parseInt(amount);
			}
			else if(isDecimal){
				value = Double.parseDouble(amount);
			}
			else if(isFraction){
				fraction = amount.split("/");
				num = Double.parseDouble(fraction[0]);
				den = Double.parseDouble(fraction[1]);
				value = num / den;
			}
		}
		
		return value;
	}
	
	public static String formatDouble(double d){
		return String.format("%1$.2f", d);
	}
	
	/**
	 * Method - getUnits
	 * Description - Returns an array of the valid units
	 * @return String[] - The array of units
	 */
	public static String[] getUnits(){
		String[] units = {
			UNIT_QUANTITY,
			UNIT_METRIC_ML,
			UNIT_METRIC_L,
			UNIT_METRIC_GRAM,
			UNIT_METRIC_KILOGRAM,
			UNIT_STANDARD_FL_OZ,
			UNIT_STANDARD_CUP,
			UNIT_STANDARD_PINT,
			UNIT_STANDARD_QUART,
			UNIT_STANDARD_GALLON,
			UNIT_STANDARD_TEASPOON,
			UNIT_STANDARD_TABLESPOON,
			UNIT_STANDARD_OZ,
			UNIT_STANDARD_POUND
		};
		
		return units;
	}
	
	/**
	 * Method - isUnitMetric
	 * Description - Checks if a unit is metric
	 * @param unit
	 * @return True - Returns if the unit is metric
	 * @return False - Returns if the unit is standard
	 */
	private boolean isUnitMetric(String unit){
		return unit.equals(UNIT_METRIC_L) || unit.equals(UNIT_METRIC_ML) ||
			unit.equals(UNIT_METRIC_GRAM) || unit.equals(UNIT_METRIC_KILOGRAM);
	}
	
	/**
	 * Method - scaleAmount
	 * @param multiplier
	 * @return 0 - Returns if the amount cannot be scaled
	 * @return double - The scaled amount
	 */
	public double scaleAmount(int multiplier){
		double result = 0;
		
		result = parseAmount(this.ingredientAmount) * multiplier;
		
		if(result != 0){
			this.ingredientAmount = formatDouble(result);
		}
		
		return result;
	} //scaleAmount
	
	/**
	 * Method - convertUnit
	 * Description - Converts between standard and metric units
	 * @return double - the new value of the amount
	 * @return 0 - if there is an error during conversion
	 */
	public double convertUnit(){
		double result = 0;
		
		if(isMetric){
			result = convertMetric();
			isMetric = false;
		}
		else{
			result = convertStandard();
			isMetric = true;
		}
		
		if(result != 0){
			// Sets the new ingredient amount if conversion succeeds
			ingredientAmount = formatDouble(result);
		}
		
		return result;
	}
	
	/**
	 * Method - convertStandard
	 * Description - Converts a standard unit to metric
	 * @return double - the new value of the amount
	 * @return 0 - if there is a conversion error due to parsing the amount
	 */
	private double convertStandard(){
		double result = 0;
		if(ingredientUnit.equals(UNIT_STANDARD_FL_OZ)){
			// Convert fl oz to mL
			// 1 mL = 0.033814 fl oz, source Google
			result = parseAmount(ingredientAmount) / 0.033814;
			ingredientUnit = UNIT_METRIC_ML;
		}
		else if(ingredientUnit.equals(UNIT_STANDARD_GALLON)){
			// Convert gal to L
			// 1 L = 0.264172 gal, source Google
			result = parseAmount(ingredientAmount) / 0.264172;
			ingredientUnit = UNIT_METRIC_L;
		}
		else if(ingredientUnit.equals(UNIT_STANDARD_OZ)){
			// Convert oz to g
			// 1 g = 0.035274 oz, source Google
			result = parseAmount(ingredientAmount) / 0.035274;
			ingredientUnit = UNIT_METRIC_GRAM;
		}
		else if(ingredientUnit.equals(UNIT_STANDARD_POUND)){
			// Convert lb to kg
			// 1 kg = 2.20462 lb, source Google
			result = parseAmount(ingredientAmount) / 2.20462;
			ingredientUnit = UNIT_METRIC_KILOGRAM;
		}
		return result;
	}
	
	/**
	 * Method - convertMetric
	 * Description - Converts a metric unit into its standard counterpart
	 * @return double - the new value of the amount
	 */
	private double convertMetric(){
		double result = 0;
		if(ingredientUnit.equals(UNIT_METRIC_ML)){
			// Convert mL to fl oz
			// 1 mL = 0.033814 fl oz, source Google
			result = parseAmount(ingredientAmount) * 0.033814;
			ingredientUnit = UNIT_STANDARD_FL_OZ;
		}
		else if(ingredientUnit.equals(UNIT_METRIC_L)){
			// Convert L to gal
			// 1 L = 0.264172 gal, source Google
			result = parseAmount(ingredientAmount) * 0.264172;
			ingredientUnit = UNIT_STANDARD_GALLON;
		}
		else if(ingredientUnit.equals(UNIT_METRIC_GRAM)){
			// Convert g to oz
			// 1 g = 0.035274 oz, source Google
			result = parseAmount(ingredientAmount) * 0.035274;
			ingredientUnit = UNIT_STANDARD_OZ;
		}
		else if(ingredientUnit.equals(UNIT_METRIC_KILOGRAM)){
			// Convert kg to lb
			// 1 kg = 2.20462 lb, source Google
			result = parseAmount(ingredientAmount) * 2.20462;
			ingredientUnit = UNIT_STANDARD_POUND;
		}
		
		return result;
	}
	
	/**
	 * Method - setIngredientName
	 * Description - Setter for the ingredient name
	 * @param name
	 */
	public void setIngredientName(String name){
		ingredientName = name;
	}
	
	/**
	 * Method - getIngredientName
	 * Description - Getter for the ingredient name
	 * @return String - The ingredient name
	 */
	public String getIngredientName(){
		return ingredientName;
	}
	
	/**
	 * Method - setAmount
	 * Description - Setter for the ingredient amount
	 * @param amount
	 */
	public void setAmount(String amount){
		ingredientAmount = amount;
	}
	
	/**
	 * Method - getAmount
	 * Description - Getter for the ingredient amount
	 * @return String - The amount
	 */
	public String getAmount(){
		return ingredientAmount;
	}
	
	/**
	 * Method - setUnit
	 * Description - Setter for the unit
	 * @param unit
	 */
	public void setUnit(String unit){
		ingredientUnit = unit;
		isMetric = isUnitMetric(ingredientUnit);
	}
	
	/**
	 * Method - getUnit
	 * Description - Getter for the unit
	 * @return String - The unit
	 */
	public String getUnit(){
		return ingredientUnit;
	}
}
